-- profiler_mgr.lua
-- Created by shenyq Jan/20/2011
-- 性能检测管理器

declare_module('PROFILER_MGR')

-- 最多统计20层调用
local MAX_SP = 20;

-- 保存每一层profiler栈的timer
local m_stack_timers = {}

-- 当前所处profiler栈的位置
local m_cur_sp = 1;

-- 输出文件
local m_file = nil;

-- 是否开始
local m_started = false;

-- 所有总结
local m_summaries = {}

-- 所有帧的profile信息
local m_profile_frames = {}

-- profile栈信息
--[[
{
    name,
    parent,
    all_time, 
    threshold,
    call_count,
    average_time,
    max_time,
    cur_frame,
    cur_frame_call_count,
    childen =
    {
        profile_stacks,
    }
}
--]]
local m_profile_root_stack = nil;

-- 当前所在的profile栈
local m_cur_profile_stack = nil;

-- peppterfish profiler
local m_pepperfish = nil;

function start(output_file)
    for i = 1, MAX_SP do
        local timer = PreciseTimer();
        timer:init();
        m_stack_timers[i] = timer;
    end

    m_file = io.open(output_file, 'w');
    if m_file == nil then
        logger:error('Fail to open file '..output_file);
        return false;
    end

    -- 栈根
    m_profile_root_stack = {
        name = 'root',
        children = {},
    };
    m_cur_profile_stack = m_profile_root_stack;

    m_started = true;
    return true;
end

function stop()
    m_stack_timers = {};

    local data = prettytostring(m_summaries);
    m_file:write(data);

    -- save to file
    io.close(m_file);
    m_file = nil;

    m_started = false;
end

function dump()
    trace(m_profile_root_stack);
end

function _summary(stack)
    -- summary self
    if stack ~= m_profile_root_stack then
        -- 计算平均时间
        stack.average_time = stack.all_time / stack.call_count;
        stack.frame_average_time = stack.all_time / stack.frames;

        -- 重新组织超过阀值的项
        local overtimes = stack.overtimes;
        local ranged_overtimes = {}
        for k, v in pairs(overtimes) do
            table.insert(ranged_overtimes, {k, v});
        end
        local function _sort(a, b)
            return a[1] < b[1];
        end
        table.sort(ranged_overtimes, _sort);
        local res = {}
        for _, v in ipairs(ranged_overtimes) do
            table.insert(res, string.format('(%d) %.3fms', v[1], v[2]));
        end

        -- 清掉一些临时数据
        stack.parent = nil;
        stack.cur_frame_call_count = nil;
        stack.frames = nil;

        -- 超过阀值的比例
        stack.overtime_perecntage = string.format('%.2f', #res / stack.call_count * 100);
    end

    for _, v in pairs(stack.children) do
        _summary(v);
    end
end

-- 总结
function summary(summary_name)
    if m_cur_profile_stack ~= m_profile_root_stack then
        error('Can not summay, current profile stack must be the root.');
    end

    _summary(m_cur_profile_stack);

    if summary_name then
        m_summaries[summary_name] = m_cur_profile_stack;
    else
        table.insert(m_summaries, m_cur_profile_stack);
    end

    -- 栈根
    m_profile_root_stack = {
        name = 'root',
        children = {},
    };
    m_cur_profile_stack = m_profile_root_stack;
end

-- 进入profile
function enter_profile(name, threshold)
    if m_cur_sp <= MAX_SP then
        local profile = m_cur_profile_stack.children[name];
        if profile == nil then
            profile = {
                name = name,
                all_time = 0,
                call_count = 0,         -- 被调用的次数
                max_time = 0,
                threshold = threshold or 20,
                parent = m_cur_profile_stack,
                children = {},
                overtimes = {},
                cur_frame = g_cur_frame,    -- 当前帧
                max_frame_call_count = 1,   -- 最多一帧被调用的次数
                cur_frame_call_count = 1,   -- 当前帧被调用的次数
                frames = 1,
            };
            m_cur_profile_stack.children[name] = profile;
        else
            if profile.cur_frame == g_cur_frame then
                profile.cur_frame_call_count = profile.cur_frame_call_count + 1;
            else
                if profile.cur_frame_call_count > profile.max_frame_call_count then
                    profile.max_frame_call_count = profile.cur_frame_call_count;
                end
                profile.cur_frame = g_cur_frame;
                profile.cur_frame_call_count = 1;
                profile.frames = profile.frames + 1;
            end
        end

        m_cur_profile_stack = profile;
        local timer = m_stack_timers[m_cur_sp];
        timer:start();

        m_cur_sp = m_cur_sp + 1;
    end
end

-- 离开profile
function leave_profile()
    if m_cur_sp > 1 then
        m_cur_sp = m_cur_sp - 1;
        local timer = m_stack_timers[m_cur_sp];
        local time = timer:stop();

        -- 记录最大时间
        if time > m_cur_profile_stack.max_time then
            m_cur_profile_stack.max_time = time;
        end

        -- 记录超过阀值的时间
        if time > m_cur_profile_stack.threshold then
            m_cur_profile_stack.overtimes[m_cur_profile_stack.call_count] = time;
        end

        m_cur_profile_stack.call_count = m_cur_profile_stack.call_count + 1;
        m_cur_profile_stack.all_time = m_cur_profile_stack.all_time + time;
        m_cur_profile_stack = m_cur_profile_stack.parent;
    end
end

-- ourself profiler
_G.PROFILE_ENTER = function(name, threshold)
    if m_started then
        enter_profile(name, threshold);
    end
end

_G.PROFILE_LEAVE = function()
    if m_started then
        leave_profile();
    end
end

_G.PROFILE_SUMMARY = function(name)
    if m_started then
        summary(name);
    end
end

_G.PROFILE_START = function(file)
    start(file or 'profiler_report.txt');
end

_G.PROFILE_STOP = function()
    if m_started then
        summary('FINAL');
        stop();
    end
end

-- pepperfish profiler 
_G.PEPPER_START = function(method, time)
    method = method or 'time';
    if m_pepperfish ~= nil then
        logger:warn('pepperfish is already started.');
        return;
    end

    m_pepperfish = newProfiler(method, time);
    m_pepperfish:start();
end

_G.PEPPER_STOP = function()
    if m_pepperfish == nil then
        logger:warn('pepperfish is not started yet.');
        return;
    end

    m_pepperfish:stop();
    local outfile = io.open( "peppterfish_report.txt", "w" )
    m_pepperfish:report(outfile);
    outfile:close()
    m_pepperfish = nil;
end

_G.MEM = function()
    print('LUA MEM:', collectgarbage('count'));
end

